﻿/**
 * Copyright (C) 2011 original author and authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using NGuice.Inject.Internal.Util;
using System.Collections;
using NGuice.Inject.Spi;

namespace NGuice.Inject.Internal
{
    /// <summary>
    /// 在注入器创建期间管理和注入实例。用于当被注入时构建请求其他实例的复杂实例，
    /// 我们使用Initializable来试图在使用之前执行注入
    /// </summary>
    internal sealed class Initializer
    {
        private readonly Thread creatingThread = Thread.CurrentThread;

        //为0则表示所有都已经被注入
        private readonly Semaphore ready = new Semaphore(1, 1);

        private int readyCount = 1;//表示当前计数

        // 存储需要注入到一个注册他们的源的可注入引用：value为InjectableReference类型
        private readonly IDictionary<object, InjectableReference> pendingInjection = new Dictionary<object, InjectableReference>();

        /// <summary>
        /// 当步骤执行时，为成员注入注册一个实例
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="injector"></param>
        /// <param name="instance">可选的具有要注入的成员的一个实例</param>
        /// <param name="source">此注入被请求的源位置</param>
        /// <param name="injectionPoints"></param>
        /// <returns></returns>
        internal Initializable<T> RequestInjection<T>(InjectorImpl injector, T instance, object source, ISet<InjectionPoint> injectionPoints)
        {
            Preconditions.CheckNotNull(source);

            // short circuit if the object has no injections
            if (instance == null || (injectionPoints.Count == 0 && !injector.membersInjectorStore.HasTypeListeners()))
            {
                return Initializables.Of(instance);
            }

            InjectableReference<T> initializable = new InjectableReference<T>(this, injector, instance, source);
            pendingInjection.Add(instance, initializable);
            return initializable;
        }

        /// <summary>
        /// 为所有注入的实例准备成员注入器。他迫使NGuice在注入的实例上进行静态分析
        /// </summary>
        /// <param name="errors"></param>
        internal void ValidateOustandingInjections(Errors errors)
        {
            foreach (InjectableReference reference in pendingInjection.Values)
            {
                try
                {
                    reference.Validate(errors);
                }
                catch (ErrorsException e)
                {
                    errors.Merge(e.Errors);
                }
            }
        }

        /// <summary>
        /// 在所有请求的对象上执行创建时的注入。无论请求的完整注入是否依赖于另一个对象，我们都首先注入。
        /// 如果两个实例是相互依存的（直接或通过传递），注入的顺序是任意的
        /// </summary>
        /// <param name="errors"></param>
        internal void InjectAll(Errors errors)
        {
            // loop over a defensive copy since ensureInjected() mutates the set. Unfortunately, that copy
            // is made complicated by a bug in IBM's JDK, wherein entrySet().toArray(Object[]) doesn't work
            foreach (InjectableReference reference in Lists.NewArrayList<InjectableReference>(pendingInjection.Values))
            {
                try
                {
                    reference.Get(errors);
                }
                catch (ErrorsException e)
                {
                    errors.Merge(e.Errors);
                }
            }

            if (pendingInjection.Count != 0)
            {
                throw new AssertionError("Failed to satisfy " + pendingInjection);
            }
            try
            {
                this.readyCount = ready.Release();
            }
            catch (Exception ignore)
            {
            }
        }

        ///

        private interface InjectableReference
        {
            void Validate(Errors errors);
            object Get(Errors errors);
        }

        private class InjectableReference<T> : InjectableReference, Initializable<T>
        {
            private readonly InjectorImpl injector;

            private readonly T instance;

            private readonly object source;

            private MembersInjectorImpl<T> membersInjector;

            private Initializer outer;

            public InjectableReference(Initializer outer, InjectorImpl injector, T instance, Object source)
            {
                this.injector = injector;
                this.instance = Preconditions.CheckNotNull(instance, "instance");
                this.source = Preconditions.CheckNotNull(source, "source");
                this.outer = outer;
            }

            public void Validate(Errors errors)
            {
                Type type = instance.GetType();
                membersInjector = injector.membersInjectorStore.Get<T>(type, errors.WithSource(source));
            }

            public T Get(Errors errors)
            {
                if (outer.readyCount == 1)
                {
                    return instance;
                }

                // just wait for everything to be injected by another thread
                if (Thread.CurrentThread != outer.creatingThread)
                {
                    try
                    {
                        outer.ready.WaitOne();
                        return instance;
                    }
                    catch (Exception e)
                    {
                        // Give up, since we don't know if our injection is ready
                        throw new ApplicationException("semapone wait exception", e);
                    }
                }

                // toInject needs injection, do it right away. we only do this once, even if it fails

                if (outer.pendingInjection.ContainsKey(instance) && outer.pendingInjection[instance] != null
                    && outer.pendingInjection.Remove(instance))
                {
                    // if in Stage.TOOL, we only want to inject & notify toolable injection points.
                    // (otherwise we'll inject all of them)
                    membersInjector.InjectAndNotify(instance, errors.WithSource(source), injector.options.stage == Stage.TOOL);
                }

                return instance;
            }

            public override string ToString()
            {
                return instance.ToString();
            }


            object InjectableReference.Get(Errors errors)
            {
                return Get(errors);
            }
        }
    }
}
